package pl.edu.agh.neurony.gui;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import pl.edu.agh.neurony.exceptions.BadFileFormatException;
import pl.edu.agh.neurony.exceptions.LayerConstructionException;
import pl.edu.agh.neurony.exceptions.NeuronNetException;
import pl.edu.agh.neurony.exceptions.UnsupportedActivationFunctionException;
import pl.edu.agh.neurony.gui.NetCreationWindow.OverallNetStructurePanel;
import pl.edu.agh.neurony.logic.Connection;
import pl.edu.agh.neurony.logic.InputNeuron;
import pl.edu.agh.neurony.logic.Layer;
import pl.edu.agh.neurony.logic.Net;
import pl.edu.agh.neurony.logic.Neuron;
import pl.edu.agh.neurony.logic.OutputNeuron;
import pl.edu.agh.neurony.logic.activationFunctions.ActivationFunc;

public abstract class NetParser {

	private static String line = null;

	static void readNetFromFile(File file, OverallNetStructurePanel overallNetStructurePanel) throws IOException,
			NeuronNetException {

		List<String[]> inputs = new LinkedList<String[]>();
		List<List<String[]>> hiddens = new LinkedList<List<String[]>>();
		List<List<String[]>> kohonens = new LinkedList<List<String[]>>();
		List<String[]> outputs;

		BufferedReader reader = new BufferedReader(new FileReader(file));

		line = reader.readLine();
		while (line != null && line.startsWith("//"))
			line = reader.readLine();

		inputs = readInputLayer(reader);
		readMiddleLayer(hiddens, kohonens, reader);
		outputs = readOutputLayer(reader);

		String[] inputValues = createNetWithPassedArgs(inputs, hiddens, kohonens, outputs);
		overallNetStructurePanel.updateSpinnersAndInputSignalData(inputValues, hiddens, kohonens, outputs);
	}
	
	private static List<String[]> readInputLayer(BufferedReader reader) throws BadFileFormatException, IOException {
		List<String[]> inputs = new LinkedList<String[]>();
		if (line == null || !line.startsWith("IN:"))
			throw new BadFileFormatException("Wrong file format. The net should have at least one input and one output"
					+ " and should begin with input definition");

		String[] input;
		while (!(input = reader.readLine().split("\\s+"))[0].startsWith("OUT") && !input[0].startsWith("HIDDEN")
				&& !input[0].startsWith("KOHONEN")) {

			try {
				for (String in : input)
					Double.valueOf(in);
				inputs.add(input);
			} catch (NumberFormatException e) {
				throw new BadFileFormatException(
						"Input line should consist of numbers only. First number is input weight and the rest are number of neurons in upper layer to which input neuron is connected to");
			}
		}

		line = input[0];
		if (inputs.size() < 1)
			throw new BadFileFormatException("There should be at least one input signal");

		return inputs;
	}

	private static void readMiddleLayer(List<List<String[]>> hiddens, List<List<String[]>> kohonens,
			BufferedReader reader) throws BadFileFormatException, IOException {

		if (line == null || (!line.startsWith("HIDDEN:") && !line.startsWith("OUT:") && !line.startsWith("KOHONEN")))
			throw new BadFileFormatException("After specyfing inputs there should be a definition of hidden layers "
					+ "or definition of otputs");

		while (line.startsWith("HIDDEN:") || line.startsWith("KOHONEN:")) {
			while (line.startsWith("HIDDEN:")) {
				List<String[]> middleLayer = middleLayerRead(reader);
				if (middleLayer.size() > 0)
					hiddens.add(middleLayer);
			}
	
			while (line.startsWith("KOHONEN:")) {
				List<String[]> middleLayer = middleLayerRead(reader);
				if (middleLayer.size() > 0)
					kohonens.add(middleLayer);
			}
		}
	}

	private static List<String[]> middleLayerRead(BufferedReader reader) throws BadFileFormatException, IOException {
		List<String[]> middleLayer = new LinkedList<String[]>();
		while ((line = reader.readLine()) != null && !line.startsWith("HIDDEN") && !line.startsWith("KOHONEN")
				&& !line.startsWith("OUT")) {
			String[] hiddenNeuronDefinition = line.split("\\s+");
			middleLayer.add(hiddenNeuronDefinition);
			if (hiddenNeuronDefinition.length < 2)
				throw new BadFileFormatException("In a definition of hidden layer neuron there should be at least"
						+ " one weight and exactly one activation function at the end of line");
		}

		checkLayerDefinitionCorrectness(middleLayer);

		return middleLayer;
	}

	private static List<String[]> readOutputLayer(BufferedReader reader) throws BadFileFormatException, IOException {
		if (line == null || !line.startsWith("OUT:"))
			throw new BadFileFormatException("Wrong file format. The net should have at least one input and one output"
					+ " and should end with output definition");

		List<String[]> outputs = new LinkedList<String[]>();
		while ((line = reader.readLine()) != null) {
			outputs.add(line.split("\\s+"));
			if (outputs.get(outputs.size() - 1).length < 2)
				throw new BadFileFormatException("In a definition of output layer neuron there should be at least"
						+ " one weight and exactly one activation function at the end of line");
		}

		checkLayerDefinitionCorrectness(outputs);

		return outputs;
	}

	private static void checkLayerDefinitionCorrectness(List<String[]> layerDefinition) throws BadFileFormatException {
		boolean correctActivationFuncDeclared = true;
		for (String[] neuronDefinition : layerDefinition) {
			correctActivationFuncDeclared = false;
			for (ActivationFunc func : ActivationFunc.functions) {
				if (func.getName().equals(neuronDefinition[neuronDefinition.length - 2])) {
					correctActivationFuncDeclared = true;
					break;
				}
			}
			if (!correctActivationFuncDeclared)
				throw new BadFileFormatException("Unsupported activation function defined: "
						+ neuronDefinition[neuronDefinition.length - 2]);
		}
	}

	private static String[] createNetWithPassedArgs(List<String[]> inputs, List<List<String[]>> hiddens,
			List<List<String[]>> kohonens, List<String[]> outputs) throws NeuronNetException {
		
		clearNet();
		LinkedList<LinkedList<Neuron>> netStructure = new LinkedList<LinkedList<Neuron>>();
		netStructure.add(new LinkedList<Neuron>());

		for (String[] input : inputs) {
			InputNeuron neuron = new InputNeuron(Layer.INPUT_LAYER);
			neuron.output = Double.valueOf(input[0]);
			netStructure.getFirst().add(neuron);
		}
		String[] signals = new String[inputs.size()];
		for (int i=0; i <signals.length; i++) {
			signals[i] = inputs.get(i)[0];
			inputs.set(i, Arrays.copyOfRange(inputs.get(i), 1, inputs.get(i).length)); 
		}
		
		for (int hiddenNr=0; hiddenNr<hiddens.size();hiddenNr++) {
			LinkedList<Neuron> layer = new LinkedList<Neuron>();
			for (String[] neuronDefinition : hiddens.get(hiddenNr)) {
				Neuron neuron = new Neuron(netStructure.size());
				neuron.setActivationFunction(neuronDefinition[neuronDefinition.length-2]);
				layer.add(neuron);
			}
			if (hiddenNr == 0)
				createConnections(netStructure, layer, inputs);
			else
				createConnections(netStructure, layer, hiddens.get(hiddenNr-1));
		}
		
		Set<Integer> kohonensLayerNumbers = new HashSet<Integer>();
		for (int kohonenNr=0; kohonenNr<kohonens.size();kohonenNr++) {
			LinkedList<Neuron> layer = new LinkedList<Neuron>();
			for (String[] neuronDefinition : kohonens.get(kohonenNr)) {
				Neuron neuron = new Neuron(netStructure.size());
				neuron.setActivationFunction(neuronDefinition[neuronDefinition.length-2]);
				layer.add(neuron);
			}
			if (netStructure.size() < 2)
				createConnections(netStructure, layer, inputs);
			else if (kohonenNr > 0)
				createConnections(netStructure, layer, kohonens.get(kohonenNr-1));
			else 
				createConnections(netStructure, layer, hiddens.get(hiddens.size()-1));
			
			kohonensLayerNumbers.add(netStructure.size()-1);
		}
		
		LinkedList<Neuron> layer = new LinkedList<Neuron>();
		for (String[] output : outputs) {
			OutputNeuron neuron = new OutputNeuron(Layer.OUTPUT_LAYER);
			neuron.setActivationFunction(output[output.length - 2]);
			neuron.bias = Double.valueOf(output[output.length - 1]);
			layer.add(neuron);
		}
		
		if (kohonens.size() == 0)
			if (hiddens.size() == 0)
				createConnections(netStructure, layer, inputs);
			else
				createConnections(netStructure, layer, hiddens.get(hiddens.size()-1));
		else
			createConnections(netStructure, layer, kohonens.get(kohonens.size()-1));
		
		createNet(netStructure, kohonensLayerNumbers);
		return signals;
	}

	private static void createConnections(LinkedList<LinkedList<Neuron>> netStructure, LinkedList<Neuron> layer,
			List<String[]> lowerLayerDefinition) {

		LinkedList<Neuron> lowerLayer = netStructure.getLast();
		for (int i=0; i<lowerLayer.size(); i++) {
			int end = lowerLayerDefinition.get(i).length;
			if (netStructure.size() > 1)
				end -= 2;
			for (int j=0; j<end; j=j+2) {
				int neuronIndex = Integer.valueOf(lowerLayerDefinition.get(i)[j]);
				double weight = Double.valueOf(lowerLayerDefinition.get(i)[j+1]);
				Connection con = new Connection(lowerLayer.get(i), layer.get(neuronIndex));
				con.setWeight(weight);
				lowerLayer.get(i).connectOutputNeuron(layer.get(neuronIndex), con);
			}
		}
		netStructure.add(layer);
	}

	private static void createNet(LinkedList<LinkedList<Neuron>> netStructure, Set<Integer> kohonensLayerNumbers)
			throws NeuronNetException, UnsupportedActivationFunctionException, LayerConstructionException {

		Net net = Net.getInstance();
		NeuralGraph graph = NeuralGraph.getInstance(false);
		
		for (Neuron inputNeuron : netStructure.get(0)) {
			net.addInputNeuron((InputNeuron)inputNeuron, false);
			graph.addVertex(inputNeuron, Layer.INPUT_LAYER, true);
		}
		
		LinkedList<Neuron> layer;
		Neuron neuron;
		for (int layerNr=1; layerNr<netStructure.size() - 1; layerNr++) {
			layer = netStructure.get(layerNr);
			neuron = layer.getFirst();
			if (kohonensLayerNumbers.contains(layerNr)) {
				net.addKohonenLayer(neuron);
			} else {
				net.addLayer(neuron, true);
			}
			graph.addVertex(neuron, layerNr, true);
			for (int j=1; j<layer.size(); j++) {
				neuron = layer.get(j);
				net.addNeuron(layerNr, neuron, false);
				graph.addVertex(neuron, layerNr, true);
			}
		}
		
		for (Neuron outputNeuron : netStructure.get(netStructure.size()-1)) {
			net.addOutputNeuron((OutputNeuron)outputNeuron, false);
			graph.addVertex(outputNeuron, Layer.OUTPUT_LAYER, true);
		}
	}

	private static void clearNet() {
		try {
			NeuralGraph graph = NeuralGraph.getInstance(false);
			graph.clear();
			Net net = Net.getInstance();
			net.clear();
		} catch (NeuronNetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
